{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "f118fabe-37b7-4cd4-b7a4-9b0fc3875ca3",
      "metadata": {
        "id": "f118fabe-37b7-4cd4-b7a4-9b0fc3875ca3"
      },
      "source": [
        "# State Schema\n",
        "\n",
        "## Review\n",
        "\n",
        "In module 1, we laid the foundations! We built up to an agent that can:\n",
        "\n",
        "* `act` - let the model call specific tools\n",
        "* `observe` - pass the tool output back to the model\n",
        "* `reason` - let the model reason about the tool output to decide what to do next (e.g., call another tool or just respond directly)\n",
        "* `persist state` - use an in memory checkpointer to support long-running conversations with interruptions\n",
        "\n",
        "And, we showed how to serve it locally in LangGraph Studio or deploy it with LangGraph Cloud.\n",
        "\n",
        "## Goals\n",
        "\n",
        "In this module, we're going to build a deeper understanding of both state and memory.\n",
        "\n",
        "First, let's review a few different ways to define your state schema."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "34e367d2",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "34e367d2",
        "outputId": "d21afb83-36e2-477f-ae1d-3f782cdb670c"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "env: GOOGLE_API_KEY=AIzaSyCD_du_wlnoyfrBSnS9k-fyqcs-C2VKqI4\n"
          ]
        }
      ],
      "source": [
        "from google.colab import userdata\n",
        "\n",
        "%env GOOGLE_API_KEY = {userdata.get('GEMINI_API_KEY')}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "cfaa61cc",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "cfaa61cc",
        "outputId": "86537869-6d12-43d8-cee7-2d68028e1e6d"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "AIzaSyCD_du_wlnoyfrBSnS9k-fyqcs-C2VKqI4\n"
          ]
        }
      ],
      "source": [
        "import os\n",
        "print(os.environ[\"GOOGLE_API_KEY\"])"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "b9a896f4-8509-456a-9a25-46532342f459",
      "metadata": {
        "id": "b9a896f4-8509-456a-9a25-46532342f459"
      },
      "outputs": [],
      "source": [
        "%%capture --no-stderr\n",
        "%pip install --quiet -U langgraph\n",
        "%pip install -q langchain-google-genai\n",
        "%pip install -q -U langchain_core langchain-community"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9f7927b0-9909-4e54-b997-ac49c1aeaa09",
      "metadata": {
        "id": "9f7927b0-9909-4e54-b997-ac49c1aeaa09"
      },
      "source": [
        "## Schema\n",
        "\n",
        "When we define a LangGraph `StateGraph`, we use a [state schema](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).\n",
        "\n",
        "The state schema represents the structure and types of data that our graph will use.\n",
        "\n",
        "All nodes are expected to communicate with that schema.\n",
        "\n",
        "LangGraph offers flexibility in how you define your state schema, accommodating various Python [types](https://docs.python.org/3/library/stdtypes.html#type-objects) and validation approaches!\n",
        "\n",
        "## TypedDict\n",
        "\n",
        "As we mentioned in Module 1, we can use the `TypedDict` class from python's `typing` module.\n",
        "\n",
        "It allows you to specify keys and their corresponding value types.\n",
        "\n",
        "But, note that these are type hints.\n",
        "\n",
        "They can used by static type checkers (like [mypy](https://github.com/python/mypy)) or IDEs to catch potential type-related errors before the code is run.\n",
        "\n",
        "But they are not enforced at runtime!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "eedb39f0-af0f-4794-bc16-65980d278b59",
      "metadata": {
        "id": "eedb39f0-af0f-4794-bc16-65980d278b59"
      },
      "outputs": [],
      "source": [
        "from typing_extensions import TypedDict\n",
        "\n",
        "class TypedDictState(TypedDict):\n",
        "    foo: str\n",
        "    bar: str"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "407a8a6a",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "407a8a6a",
        "outputId": "ae69d914-edf7-4c73-b33f-2f94ff174d95"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "M&Ms\n",
            "Choco\n"
          ]
        }
      ],
      "source": [
        "choco_bars: TypedDictState = TypedDictState(company=\"Choco\", bar=\"M&Ms\")\n",
        "print(choco_bars[\"bar\"])\n",
        "print(choco_bars[\"company\"])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d5a71661-1086-455f-a5e0-a6d104034a95",
      "metadata": {
        "id": "d5a71661-1086-455f-a5e0-a6d104034a95"
      },
      "source": [
        "For more specific value constraints, you can use things like the `Literal` type hint.\n",
        "\n",
        "Here, `mood` can only be either \"happy\" or \"sad\"."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "4ad9749c-b127-433f-baa3-189a9349e9f6",
      "metadata": {
        "id": "4ad9749c-b127-433f-baa3-189a9349e9f6"
      },
      "outputs": [],
      "source": [
        "from typing import Literal\n",
        "\n",
        "class TypedDictState(TypedDict):\n",
        "    name: str\n",
        "    mood: Literal[\"happy\",\"sad\"]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "d484d218",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "d484d218",
        "outputId": "f3463e6e-9159-4811-ef46-d918bb6275fb"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "{'name': 'Lance', 'mood': 'mad', 'random_field': 'user'}\n"
          ]
        }
      ],
      "source": [
        "override_mood: TypedDictState = TypedDictState(name=\"Lance\",mood=\"mad\", random_field= \"user\")\n",
        "override_mood[\"mood\"]\n",
        "print(override_mood)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c1a9152d-1728-4a67-9e23-1ef622525047",
      "metadata": {
        "id": "c1a9152d-1728-4a67-9e23-1ef622525047"
      },
      "source": [
        "We can use our defined state class (e.g., here `TypedDictState`) in LangGraph by simply passing it to `StateGraph`.\n",
        "\n",
        "And, we can think about each state key just a \"channel\" in our graph.\n",
        "\n",
        "As discussed in Module 1, we overwrite the value of a specified key or \"channel\" in each node."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "2f7a0d6d-f70b-44ed-86e3-7cdb39873ba4",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 350
        },
        "id": "2f7a0d6d-f70b-44ed-86e3-7cdb39873ba4",
        "outputId": "ad304ae1-f3fe-45bf-b7d0-3407476abb63"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAFNAOYDASIAAhEBAxEB/8QAHQABAAMBAQEBAQEAAAAAAAAAAAUGBwQIAwECCf/EAFYQAAEEAQIDAgcLCAUJBQkAAAEAAgMEBQYRBxIhEzEIFBciUVaUFRYjMkFhdZWz0dM2N0JUVXF00jVSgZOyJCUncpGhsbTBCTNDgtQYRUZXZIOk8PH/xAAbAQEBAAMBAQEAAAAAAAAAAAAAAQIDBQQGB//EADgRAQABAgIHBQQKAgMAAAAAAAABAhEDUQQSFCExUpFBYXGh0QWBksETFSMyM0JDYrHhIvBTsvH/2gAMAwEAAhEDEQA/AP8AVNERAREQEREBERARfy97Y2lziGtaNySdgAqwwXNa/DNs2cZgt/gxXd2U90f1i8edHGfk5eV579wOh2UUa2+ZtELZP3MpSx5AtW4K243HbStZ/wASuX31YX9sUPaWfeuWloLTeP3MGCx7ZD1dK6u18jz6XPILnH5ySur3q4X9j0PZmfctn2Mds+X9ruPfVhf2xQ9pZ96e+rC/tih7Sz7096uF/Y9D2Zn3J71cL+x6HszPuT7Hv8jce+rC/tih7Sz7099WF/bFD2ln3p71cL+x6HszPuT3q4X9j0PZmfcn2Pf5G499WF/bFD2ln3oNU4UnYZehv/Es+9Perhf2PQ9mZ9ye9XC/seh7Mz7k+x7/ACNyQgsxWoxJDKyaM/pRuDh/tC+irk/D/CF/bUqjcNcA2bbxYFeQdd+vKNnDf5HAjqdwvti8pbp5FuJy5a+w9rn1LsbeVlpg72kfoytHUt7nDzm/pNZJopmL4c37u1LZJ1ERaEEREBERAREQEREBERAREQEREBERBWddu8ap43D7gNzF1lOQbnrCGPllb0/rRxPb/wCZWVrQxoa0BrQNgB3BVrWTewyGmMgQeyqZRrZCBvsJYZYG/u8+WPqrMvRX+HREcN/W/pZZ4QIiLzoo+oONejNL6yraUyOZMefn7ECpDUnn7PtX8kXavjY5kXO7o3nLd/kVf4f+EHitd8TtY6MZRv1LeCveJwzOoWuzsBsLJJHukMIji2c5zWtc7d4aHN3DgqJxl92NO8Wm5jh7g9WRa3tux9e3JBjjNgcxVEuzm2ZTu2J0Ub5NpN2OHcOYHpLaZtZ7RHGXivjmaby01nUtqHJ4TKtovkxryzHRx8s07fNiIlhLSHEE8zdu/dBe9E8fdB8RNQHCYHPeNZTsnTxwTVJ63bxtIDnxOljaJWjcblhcOqqmo/C30NW4c6j1Tpyza1M3E42W+2OvjbjIZHMIYInTdgWsdzvYHA9WtJeQGgkZDw7xupclxS4OahzGK4h3sxSdci1PkNQV5m06lqek9vJBD0YyHtQR2kTOQN7PmfuQrroThznbXgJXdHx4exR1Fb0/k68eNtxGvKZ5HTlrXNeAWlxcO/b426DceHmu6HEbS1TN45lqOGUAPZcpT1HNfyguAZMxji3r0cBsfkJVlVJ4P6t992h6Er8JmsDPUiiqzVM5j5Kcoe2NnMWteBzN3JHMOhIOyuyAq9r2pJPpe7Yrhvj1BpvVHO3G00YLm9R8h2LT8ziNjvsrCoXWl8YzSWYs8rnuZUk5GNG7nvLSGtA+UkkAfvW7BvGJTbOFjik6NyPIUq9qEkxTxtlYT38rhuP+K+64cHjzicJj6JIca1eOEkdx5Wgf9F3LXVaKptwQREWIIiICIiAiIgIiICIiAiIgIiIOPL4qvnMXax9tpfXsxuieGnYgEd4PyEd4I7iAVFYrPPoWYsRm5Y4ciTy17B82O8PkLN/09h50feDuRu3YqwrmyGNq5apJVu1ordaT40UzA5p/sK201xbVq4fx/vmvipOU8H7hlm8lbyOQ0Bpu7ftyunsWbGLhfJLI4kue5xbuSSSST6Vzv8G/hTK4F/DjS7yAG7uxMB6AbAfF+QABT40DBX6UcxmsfH8kUV50jG/uEvPsPmHQJ7ybHrVnv76H8JZ6mHPCvyn+y0ZpbTum8VpHDV8ThMbVxGLrcwhp0oWxRR8zi48rWgAbucT+8lSSq/vJsetWe/vofwk95Nj1qz399D+En0eHz+UlozWhFlelMflczrDW2Ms6pzArYe7XgqmOWHmLX1IZXc/wffzSO27um371bPeTY9as9/fQ/hJ9Hh8/lJaM37rDhfo/iFNWl1PpjE6glqtc2B+SpxzmIO2JDS4HbfYd3oVd/wDZq4Tb7+TbS31RB/KrD7ybHrVnv76H8JPeTY9ac8f/AL0P4SfR4fP5SWjM0rw80dwyhvz6d09h9MRWGtdbkoVY6zZAzm5S8tA3DeZ22/duUYffrfqztYRgKconjdI0tN2dpBY9oP8A4TD5wP6bg1w81oL/AKQaBxYljlvOt5mWMgs907L52NIO4IjJ5AQeu/Lv0HXoFZE1qMP7k3nPLwN0cBERedBERAREQEREBERAREQEREBERAREQEREBERAREQZ7w/IPEnijsSSMnS3+r6/z/ctCWe8P9/KTxQ7v6TpdwG/9H1+/b/qtCQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERBnnD4bcSuKXnA/5zpdAO7/N1bvWhrPOHu3lK4pbHr7p0t+m3/u6t/tWhoCIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiquW1XedkJ6ODo17klY8lmzcndFFG8gEMbyscXu2IJ7gNx1J3A2YeHViTalbXWpFSPd3WH6hg/a5vw093dYfqGD9rm/DXo2WvOOsFl3UXqrK28FpjMZKhjnZe9TpzWK+PY/kdakYwubEHbHlLiA3fY7b9xVc93dYfqGD9rm/DT3d1h+oYP2ub8NNlrzjrBZ5K8HHw47nFTjpkMDjOHUsU+p70VieV+WG1CGGtHFI93wA59hESASNyQ3cd692LzRwn8H6bg/xM1trTD4/DG9qWQOEDrErWUmE88kce0fxXyed82zR8nXYPd3WH6hg/a5vw02WvOOsFl3RUj3d1h+oYP2ub8NPd3WH6hg/a5vw02WvOOsFl3RUkZ3V4PXH4Qj0C5MN/wC3suintPahGaFiGaA08hVcG2Kxdzhu+/K5rthzMcAdjsO4ggEEDXXgV0RrTa3dNyyYREXnQREQEREBERAREQEREBERAREQEREBZ9pQ7vzxPecva6/+fZaCs+0n8bO/S9v7Qr36P9yv3Mo4SnkRFsYiIiAih9KauxOt8OMphbfjtAzzVu17N8fwkUropG7PAPR7HDfbY7bjcbFTCgIv4llZBE+WV7Y42NLnPedg0DvJPyBRumNUYrWmDr5jCXo8li7BeIbUO/JJyvcxxaT3jmadiOh7xuCCglVG6cP+kbND04qlv8/w1r/9/tKklGad/OPmvoml9taWf6eJ4fOFjtXZERcpBERAREQEREBERAREQEREBERAREQFn2k/jZ36Xt/aFaCs+0n8bO/S9v7Qr36P9yv3Mo4Snl5f1tXzOe1F4QFxur9R4w6Wr17eHrY/JyQQVpvcuOYuLG9HtL2gljt2dXHl3cSvUCrcvDnTs8mq5H4/mfqmNsWYPbyf5U0Q9gB8bzPgxy+Zy+nv6rKYuxYNpu5meOmo9UOzGsM1patgsFibFOPB3nUmCW1T8YltS8v/AHgDyWhrt2ARu6Hcrg4c6x1H4QeX0Ph87qDLacre8uvqG0zB2nUbGStSTvg5zIzzhE0R83I0gEyjfoAFtGo/B74f6sbj25PT4m8RosxsRiuWIS+qz4sEpZI0zMH9WTmHU+krt1bwV0XrduHGVwcZdh4+yx8lKeWnJWj2A7Nj4XMcGbADk35encsNWR5S0FlNV2MDoHh3gbVl1a5Y1JesS+7j8TYvvgycjAzxqKGR4ID3SOaxrS7odwBsfUHBDB6107pvIUtaW47crb73Y4+6Dr80dQtZyxyzuiiMjmv7Tzi3flLQSSN1+TeD1w+n0fjtLnTrGYbG2ZbdGOOzOyWrLI9z3uima8Ss3c93RrgNjt3ABdA0HmdG4ehh+HlrB4DFQdo+WHL0LOQe973cxcHi1G7ckuJLuYknvViJgWXVukMTrrBy4fOVBfxcz2Pmqvc4Ml5XBwa8AjmbuBu09COhBBIWZeB40M8G3RTWgNaIZwAB0A8ZlWhaSqauqvte+jK4XJMcG+LjEYyamWHrzc/aWJebfptty7bHv36dWj9H4jQOm6WAwNTxDE0w5sFftXycgc4uPnPJceriep+VZW33EyozTv5x819E0vtrSk1Gad/OPmvoml9taWz9PE8PnCx2rsiIuUgiIgIiICIiAiIgIiICIiAiIgIiICz7Sfxs79L2/tCtBVGyuPyGlr9y1SpjJ4+9YExibYZFLDM/lbyt7Rwa5rnbbDcEOcR13G3t0aqLVUTNrsoySyKu0NR5rI1I7MWis0yOQbtE8lWJ+3zsfMHD+0Bff3Wz3qZlfaqX469Wp+6Pij1LJtFCe62e9TMr7VS/HT3Wz3qZlfaqX46an7o+KPUsm0UJ7rZ71MyvtVL8dPdbPepmV9qpfjpqfuj4o9SybRQnutnvUzK+1Uvx091s96mZX2ql+Omp+6Pij1LJtRmnfzj5r6JpfbWlHz6my9a1FXm0hlYXSsc9ssk9UQjZzW8rpBMQ1xL2gNPV3XYHldtZNLYO3UtXcpkeRl+62OPxeJ5cyCFhcWN3/Sdu95cQAOoA35eY41zGHh1RMxvi26YntjI4LEiIuUxEREBERAREQEREBERAREQEREBEUPkM65t447GRwX8pE6B9mu+cRitBI9w7V/Qn4scnK0DznNA3aCXtD6ZnPw4kdgxvjuVlgmnqYyGRjZ7XZgFwYHuaB1cxvM4hoL27kbhc0OAfkbTbuaMdp7XwWK9AtbJDRmYwgujcWhz3cz3ee7boG7NaQSezD4ZuLiPaWJb9pz3vfas7GQ8zy7kGwGzBuAGjuAHedyZFAREQEREBERAREQc2SxtTM4+zQv1Yb1G1G6GerZjEkUsbhs5j2kEOaQSCD0IKi+zyODtNEQnzFK3caCxzmNdj4jHtu09DIznaCQSXDtHbEhoaJ1EHLi8rTzmOrX8faiu0bLBJDYgeHskae4gjoQupQOUxtzGSWMphxJYsNrub7jmZsVaw7tO0L+rTyS9ZAHAgOL/P35Wlslj8tTyptCpYZO6rO6tOxp86KVoBLXDvB2LXDfva5pG4IJDsREQEREBERAREQEREBERAREQROZyFmOarRo13Tz2X8k0sc8bDTiLXEzkO3LureVoDXbuc3fZvM5vXi8azF02QNllsPAHaWJyDLM7YAveQACTsO4ADYAAAACH0dELrL2alGInt355GNu4lxkbNVjlkFYOkPxnBh3cB5rXveG7/ABnWNAREQEREBERAREQEREBERAURm6VpnLkccZn3KokkFGOZsUV4mMtEchc123UMIcNnAtA35S5rpdEHNjrrcjTinawxOcNnxOexzonjo5jiwubzNILTsSNwepXSq5SiGH1paqwjEVamTrG8II3Fl2eyxzWTSlnc9gY6s0uHVp2B35m7WNAREQEREBERARFC5jW2ntP2hWyecx2Pskc3Y2bTGP29PKTvss6aKq5tTF5W100iq3lS0d604j22P708qWjvWnEe2x/etuz43JPSV1ZyWlRWo9WYPR1GO7n8zj8HTkkELLGStMrxukIJDA55ALtmuO3fsD6FF+VLR3rTiPbY/vWU+FBidEceeDGd0v758N7pcnjeNlddj+DtxgmP9LoHbuYT6HlNnxuSekmrOS/8I9d6bz+Bo4rGah0lkcrBXMs1HStyOSCNvPsXMjaeZrN3N6kd7vnWgLw5/wBnfoLTfB7QeS1LqXLY3G6rzsnZeLWrLGTVasbjysIJ3aXu3cR6AxeuvKlo71pxHtsf3ps+NyT0k1ZyWlFVvKlo71pxHtsf3p5UtHetOI9tj+9NnxuSekmrOS0oq7T4jaVyFiOCtqTFTTSODGRsuRlznHuAG/U/MrEtVdFeHuriY8UtMcRERYIIiICIvhev1sXUltXLEVSrEOaSed4Yxg9JcegViJmbQPuiq7uKOj2kg6oxAI6EeOx/evzypaO9acR7bH9637Pjck9JZas5LSiq3lS0d604j22P708qWjvWnEe2x/emz43JPSTVnJWs5xY0TQ4h45tnWOiK7qVa9Ut+O5KBuRrzdpB8FGS7zGbxv7Vp2PMyLp5p20eldr5OlXuU7EVupYjbLDPA8PjkY4btc1w6EEEEEdCCv80uNPgzac1h4YuPv0c1jjoPUc5y+XuR22claQO5rERdudnSu6t+eQ/1Sv8AQetxJ0RSrRV6+pMLBBEwRxxR242tY0DYAAHoAPkTZ8bknpJqzktqKreVLR3rTiPbY/vTypaO9acR7bH96bPjck9JNWclpRVbypaO9acR7bH96ncXmaGcrGxjrte/AHFhlrStkaHDvaSD3j0LCrCxKIvVTMe5LTDsREWpHFmrjsfh71pgBfBBJK0H0taSP+CqOkqkdbAUpAOaezEyeeZ3V80jmgue4nqSSf7O7uCs+qvyYzH8HN/gKr2mvycxX8JF/gC6GBuwp8V7EkiIs0EREBERAREQfOzWhu15ILETJ4JGlr4pWhzXA94IPQhOHduWxp6SKWR8vilyzUY+Rxc4xxzPawEkkkhoA3J3O25719Fy8NP6GyP0te+3epib8GfGPmvYtqIi5qCIiAqRni3J6+hp2B2tejQZbiicN2iV8j2c+3cSGs2BI6cztu8q7qjX/wA5tv6Hr/bTr2aL96qe5YSyIi3oIiICIiAiIgKGscuL1pp2zXAimyM8lCyWDbtoxXmmbzektdF5pO5HM8DYOdvMqEzP5U6K+lZf+QtrZR+aO6f4lYX5ERchEXqr8mMx/Bzf4Cq9pr8nMV/CRf4ArDqr8mMx/Bzf4Cq9pr8nMV/CRf4Aujg/gz4/JexJLCtE+EplNTUNC5rJaIOF01q603H1LwyrLE0VlzJC0PhEY+DcYnND+bfu3Y3fZbqsIwPAjP4vhRwl0xLcxrr+ks1UyV6RkshikjiM3MIiWbl3wjdg4NHQ9QpN+xH1d4S9nxV+pW6Pmdw2Zlfcp2pfdBna7+MeLGwKvJuYBN5vNz82wJ5NlVeP/HTUuQ4ccThorT9v3IwEc+Os6riywpyw22bdr4vGG8zxGSA5/MzqHBvNspCbwftZv0pJw1bk8G3hrJlTdNz4b3UFQ2/GjV7Pl7PfnPJ2vP8AF/Q3XPrPgNxFfpXiPozTN7TM2ltWWrd+GbLSWIrdKWy7nli2jY5rmc/MWu3BHN1DttlhOtYTWv8AwqMdorVV7TlKDC3ruJrwyZF2Z1LWxJ55IxI2OBsu5mfylpPxWjmA5t9wNc0HrPH8RNGYXU2K7T3PytWO3C2ZvK9rXDflcOuxHcdieoWX3+Fet9J6/wBR5/RUmmb9PUjK8l2nqMTNNS1FEIu1hdE13O1zWt5mO5erejhurpkeLGD0na9ycpDmHZCsxgnOM01kbFYuLA49m+KB7COvyOO3ceoKziZvvFT8IrT7Kels9rO3rjU2nYMRin+KUsNf8Vh8a3dyOc1o3me97o2BjyW9wA3JWi8O5c3PoDTUmpWhuon42s7JNDQ3ayYm9r0HQefzdAsf4n4PW/GbPaOzOjocJZ0hhbBvvxmqhfxstq8zcROkidW5uzi6PbvsHOO56NC2vSr85JgKjtSw4+vmyHeMx4qWSWs08x5eR0jWuPm8u+7R13SOIlly8NP6GyP0te+3eupcvDT+hsj9LXvt3rPE/Bq8Y+a9i2oiLmIIiICo1/8AObb+h6/206vKo1/85tv6Hr/bTr2aLxq8PnCx2pZZzr/illtMa8wGksHphmoMnmaNu7FJNkRUhh7B0IIkJY88pEve0OO4A5diXN0ZUTN6Ev5LjNpXV0U1ZuNxWKv0Z4nucJnPnfXcwtHLsWgQu33IPUbA9dt037EU+v4SEmUwOnWYrSs1zWeZyN3FM09JdZEyvNTc5tp0ljlIEbOUEODSXc7AG7nok8JPxbC2q1jStpuvIc2zTzdLRW2PMtt8QmY5tjYN7EwntDIWjYA7t36GHq8BNWaes09Q4a9hn6nxeps3lqte4+XxSxSyEji6GR7Wc8cgHZu3a1wDmbecDuvhY8HrV875tZ+7GGZxNdqKPPtbySnGBjK3ijaZdt2hb2JO8nKDzforX/kODD8dcrofVPGHOa9pzYmPFMwsVbBsyzbNdkszJWtEMjuRjBI4sLnEM22Jd0burpwk8I+lxK1nPpaxXxFfLCi7IwvwWfgy9d8TXtY9rpIw0xyAvZ5pbsQSQTsVWMl4Pmsdcv1/kdR5HB4jN5qbD3cVJiHTWYqlmgXub2gkYznaSWg7d4Lug2G+g6ezGq9H0MhluIFHA1asbYooI9H07t+YvJIe5zRFz8p8zZrWHl2JLj8iLjRbtY3KViu2aWsZY3RiaAgSR7jbmaSCAR3jcFYJweu2cZx71ZpXGanz2b07jMTE65Bqi2+ayzIGct565lAkdCYwd3NHZ8xbyn5BoVfi5jNTmTF6fbl4M3YikbTky2mcnBVbKGEtMr3wMaG7jru4b9wO5Cr+juHetsjxYra61xZwFWxj8RLialDTpmkbIJZGPfJLJK1p/wDDGzACBuTv6cp32sNfUJmfyp0V9Ky/8hbU2oTM/lTor6Vl/wCQtrfR+bwq/wCsrC/IiLkIi9VfkxmP4Ob/AAFV7TX5OYr+Ei/wBWnM03ZHEXqjCA+eCSIE/IXNI/6qoaSuR2MDThB5LNaFkFiB3R8MjWgOY4HqCD/tGxHQhdDA34Ux3r2JhERZoIiICIiAiIgLl4af0Nkfpa99u9fW3bgoV5LFmaOvBG0ufLK8Na0DvJJ6AL+uHlOWtp58ssT4TbuWbbI5GlrhHJM9zNwQCCWkHYjcb7HuUxN2DPjHzXsWZERc1BERAVGv/nNt/Q9f7adXlUjP8uL15Fdsnsq12gypHM47M7Vkj38hPcCQ8kbnrynbuXs0X71UZwsJRERb0EREBERAREQFCZn8qdFfSsv/ACFtTahZizK600/WruE0uNnkv2eQ7iFhrzQt5vQXOl6A7E8riNw07bKPzT3T/ErC+oiLkIKFzGitP6hsCxlMHjcjOByiW1UjkeB6N3AnZTSLKmuqib0zaTgq3kr0Z6p4T6vi/lTyV6M9U8J9Xxfyq0ot20Y3PPWVvOareSvRnqnhPq+L+VPJXoz1Twn1fF/KrSibRjc89ZLzmx3gfw70vleFGm7d7T2KvW5a5MlixTike887huXEHfu9KvPkr0Z6p4T6vi/lUPwH3i4a1KjnbyUL2RoP7+hhuzxbdf8AU+7otBTaMbnnrJec1W8lejPVPCfV8X8qeSvRnqnhPq+L+VWlE2jG556yXnNXqPDvSuMsx2Kmm8TWnjcHMkipRtc1w7iCG9D86sKItVddeJN65v4l7iIiwQREQF8LtKvkqsla3XitVpByvhmYHscPQQehX3RWJmJvAq7uFujXOLjpTCkk7k+58X8q/PJXoz1Twn1fF/KrSi37Rjc89ZW85qt5K9GeqeE+r4v5U8lejPVPCfV8X8qtKJtGNzz1kvObHs7w60tFxm0fTj09io6c2Gy0s1RtOIRyuZLQDHubt1Led4B2O3O7qN+t48lejPVPCfV8X8qiL73W+PmEY13mUNM33SN3PfPaphh9HdWk+fv2+VaAm0Y3PPWS85qt5K9GeqeE+r4v5U8lejPVPCfV8X8qtKJtGNzz1kvOareSvRnqnhPq+L+VT2MxFHCVvF8fSr0a/MX9lWibG3mPedgB1PpXWiwqxcSuLVVTPvLzIiItSCIiAiIgIiIM90IDpvX+tNOSM7OG1YZn6B67PinaGTtHyFzbEcjjt3CeP07nQlWNcaTm1BFQv4yeOlqLEymxjrUgPISWlr4ZdupikaeVw+Qhrx5zGEdGktYV9VQWIzC/H5ak4R38XYI7aq8jpvt0c12xLXjzXDqD37BPoiICIiAiIgIiICIiAiIgIio+qM3b1Vds6U05Ylgn25Mrm6583GRnvjjd3OtOHxWj/ugRK/beJkwc3D5p1BrnWuqizatJNDg6MhB+Egqdp2jxv/8AUz2W7jvEbT1G22griw2GpadxFLF42tHTx9KFlevXiGzY42gBrR+4ALtQEREBERAREQEREBERAREQFWtV6Fpams1cjHLLis/SBFPMUiGzxNJ3Mbt+kkTiBzRvBadgdg5rXCyogoFbiHd0lPDj9fQ18a6R4ig1DUaW4u04kBodzOc6rI4nYMlcWkkBksjiQL+vlZrQ3a0texEyevKwxyRStDmvaRsWkHoQR02KoQ0bm9AP7bRkrb2H5t5NL5Gctiib8ppzEOdCfRE/eI7BrexBLkGhIvLPDXw8dNcR/CJt8PYK0dbDzV44sXlXzDmnvBpdPC7Ylm3Xs2FjnAuiJa54lby+pkBERAREQERcObzVHTeHvZXJ2WU8dShfYsWJPixxtBc5x/cAUHco7UGosZpXFTZPL3oMdQh2557Dw1u5OwaPS4kgBo6kkAAkrzTwA8Nw+ENPnMJp7Srm6oqWJZKzLNtkVTxAv2jsyuJMgLd2MeyNjyXOYRs157PdsBw7bBlYs7qO+dSajYPg7MkZjq09x1FWuXOEIPXziXyEHZ0jgAAEaZtS8SncsIt6O0o8dZ3h0OXvNI7msc3emw/1nfDHc7NhcA43TBYHH6ZxNfGYqnFRoVwRHBC3Zo3JJPzkkkknqSSSSSV3ogIiICIiAiIgIiICIiAiIgIiICIiAojVuRx2L05fly1iWtQdEYpH15ZI5vOHLtG6Mh4ed9mlhDgdtiCpdee+I2pZNUawtsDycfipHVK8YPQyjpNIR6ebdg9AadtuYrpaBoc6Zi6k7ojfKvPeQ8Fbh4dTVctp3D3tLtpzCauWZGSSfmad2P3DtoyCN9gXf6y3k671kf8A4rtj/Vp1NvsVEIvuaNC0bDjVjDj3xE/zdjrSl/f1rL1sueyVPwU9/WsvWy57JU/BUQi2bNo//FT8MehrSl/f1rL1sueyVPwU9/WsvWy57JU/BUQoXVGrKekmYt1yOeQZHIQY2LsGg8skp2aXbkbNG3Ujc/MVKtH0amLzh0/DHoa0rj7+tZetlz2Sp+CqzxIr5jiro67pjUGpL1rEXOXt4WRwwmQAhwBMbGnbcA7b9duq70V2XR5/Tp+GPQ1pVXweOCfDHgvrXH5uXCzwZuvzx1s6b8roYzIx0bu0iLtmAtcRzHmaObc8uwI9oLyy5ocCCAQehB+VavwR1LLapXcBZeXvxoY+q5x6mu/cNZ8/I5rh8zSwL5r2p7NowqJx8GLRHGPnC8WnoiL5UEREBERAREQEREBERAREQEREBERAXlaPnE94Sb9q27ZbJv8A1hM8O/3gr1SsE4o6Wk01qmxfZHti8rIJWyAdI7B6PYfRzbB4Pylzx8g3+k9iYtNGLVh1cao3e7sXsVRFGagxV3L1I4aOat4OVsgebFOKGRzhsRykSxvbt1B6DfoOveoAaJ1AAf8ASFnDuPlp4/p/+MvsKqpibRTM9PVrcfHnKZPDcItS3MPJJBdjgb8NCCXxRmRoleNtju2MvO4II27wswx+hK+Gx2ZyWI1Lpt1V2nrrp8dgIZWeOxuiPJLJz2Zdy122z9t/OIJ6raMNpbLY682a7q7KZmvyua6nbrU2Rv3G3UxwMd0/euvG6J07ho7bMfgMXRZcaWWW1qccYnae8PAaOYH0FeTEwJxq9eYt49nfunt7fBWK6ewVbSeQ4RZLCVuxymYxc7L0naOLrx8Q7Zvaknd20jQQT3dw2CrGIxumb2leGeo/GYrutL+pKDslamsk2nTGU9rG9m/QMI2DdtgANgvTzcHjWOx5bj6rTjgW0yIW/wCTAt5CI+nmDl83zdunTuUedB6aOTdkve9ivdJ0onNzxGLtjIDu1/Py78wPUHfdap0OeEWt/Ub/AB3eYnUVM95GoP8A5h532PH/APpl+nRGoCfzhZwfMKeP/wDTL3a9XJPl6ouSt/BrmPEaXl35Bipufr03M0PL/wAH/wC9Utn+S1W9tMX9mzz5pNgTsOrjsAB6egAWzcGtJT4XGW8veidBdynIWQyAh0VdoPZhwPUOJc9xHQjmaCN2lc/2pjU4Wi1RVxq3R/vczpzaKiIvz0EREBERAREQEREBERAREQEREBERAXLk8XUzVCalerx2qkzeWSKQbtcP/wC9QfkI3XUisTMTeBjmc4H5CvK5+BycM8JJIq5TmaW/MJWAkj97CfSSoQ8J9ZA7eJYw/O2+7b/fEt+Rdqj2xpVEWmYnxj/xfcwDyUay/Ucb7e78NPJRrL9Rxvt7vw1v6LZ9daTlHSfU3ZMA8lGsv1HG+3u/DTyUay/Ucb7e78Nb+ifXWk5R0n1N2TAPJRrL9Rxvt7vw1/cfCPWMpANfEwel0l55A/sEXX/ct8RPrrSco6f2bsmcaQ4NVcPchv5m2MxchcJIYWxdnWhcOocGEkvcD1BcdgdiGggFaOiLkY+kYuk1a+LVeQREXnQREQEREBERAREQf//Z\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "import random\n",
        "from IPython.display import Image, display\n",
        "from langgraph.graph import StateGraph, START, END\n",
        "from langgraph.graph.state import CompiledStateGraph\n",
        "\n",
        "\n",
        "def node_1(state: TypedDictState):\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"name\": state['name'] + \" is ... \"}\n",
        "\n",
        "def node_2(state: TypedDictState):\n",
        "    print(\"---Node 2---\")\n",
        "    return {\"mood\": \"happy\"}\n",
        "\n",
        "def node_3(state: TypedDictState):\n",
        "    print(\"---Node 3---\")\n",
        "    return {\"mood\": \"sad\"}\n",
        "\n",
        "def decide_mood(state: TypedDictState) -> Literal[\"node_2\", \"node_3\"]:\n",
        "\n",
        "    # Here, let's just do a 50 / 50 split between nodes 2, 3\n",
        "    if random.random() < 0.5:\n",
        "\n",
        "        # 50% of the time, we return Node 2\n",
        "        return \"node_2\"\n",
        "\n",
        "    # 50% of the time, we return Node 3\n",
        "    return \"node_3\"\n",
        "\n",
        "# Build graph\n",
        "builder: StateGraph = StateGraph(TypedDictState)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "builder.add_node(\"node_2\", node_2)\n",
        "builder.add_node(\"node_3\", node_3)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_conditional_edges(\"node_1\", decide_mood)\n",
        "builder.add_edge(\"node_2\", END)\n",
        "builder.add_edge(\"node_3\", END)\n",
        "\n",
        "# Add\n",
        "graph: CompiledStateGraph = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "724bb640-2b0e-46c1-9416-b5bcdb9c17c8",
      "metadata": {
        "id": "724bb640-2b0e-46c1-9416-b5bcdb9c17c8"
      },
      "source": [
        "Because our state is a dict, we simply invoke the graph with a dict to set an initial value of the `name` key in our state."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "74e09d32-6a08-4250-b19a-1f701828829d",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "74e09d32-6a08-4250-b19a-1f701828829d",
        "outputId": "4c60dff9-804b-4651-b31d-d8172c29426b"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n",
            "---Node 2---\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'name': 'Lance is ... ', 'mood': 'happy'}"
            ]
          },
          "metadata": {},
          "execution_count": 9
        }
      ],
      "source": [
        "graph.invoke({\"name\":\"Lance\"})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "70cc5368-18b8-49c7-b561-41888b092311",
      "metadata": {
        "id": "70cc5368-18b8-49c7-b561-41888b092311"
      },
      "source": [
        "## Dataclass\n",
        "\n",
        "Python's [dataclasses](https://docs.python.org/3/library/dataclasses.html) provide [another way to define structured data](https://www.datacamp.com/tutorial/python-data-classes).\n",
        "\n",
        "Dataclasses offer a concise syntax for creating classes that are primarily used to store data."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "d576fc2c-350b-42ad-89e5-f93ae102dbf8",
      "metadata": {
        "id": "d576fc2c-350b-42ad-89e5-f93ae102dbf8"
      },
      "outputs": [],
      "source": [
        "from dataclasses import dataclass\n",
        "\n",
        "@dataclass\n",
        "class DataclassState:\n",
        "    name: str\n",
        "    mood: Literal[\"happy\",\"sad\"]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "bc104376",
      "metadata": {
        "id": "bc104376"
      },
      "outputs": [],
      "source": [
        "# no_name: DataclassState = DataclassState(mood=\"mad\")\n",
        "# TypeError: DataclassState.__init__() missing 1 required positional argument: 'name'\n",
        "\n",
        "\n",
        "# no_name: DataclassState = DataclassState(mood=\"mad\", name=\"hi\", random_field= \"user\")\n",
        "# TypeError: DataclassState.__init__() got an unexpected keyword argument 'random_field'"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "64482b93-3c8f-4a30-925f-9be64e4b8b6f",
      "metadata": {
        "id": "64482b93-3c8f-4a30-925f-9be64e4b8b6f"
      },
      "source": [
        "To access the keys of a `dataclass`, we just need to modify the subscripting used in `node_1`:\n",
        "\n",
        "* We use `state.name` for the `dataclass` state rather than `state[\"name\"]` for the `TypedDict` above\n",
        "\n",
        "You'll notice something a bit odd: in each node, we still return a dictionary to perform the state updates.\n",
        "\n",
        "This is possible because LangGraph stores each key of your state object separately.\n",
        "\n",
        "The object returned by the node only needs to have keys (attributes) that match those in the state!\n",
        "\n",
        "In this case, the `dataclass` has key `name` so we can update it by passing a dict from our node, just as we did when state was a `TypedDict`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "1e1eda69-916f-4f6e-b400-6e65f73d8716",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 350
        },
        "id": "1e1eda69-916f-4f6e-b400-6e65f73d8716",
        "outputId": "33a60bc8-72a1-432e-ebe0-b4ca5b886167"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "def node_1(state: DataclassState) -> dict:\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"name\": state.name + \" is ... \"}\n",
        "\n",
        "def node_2(state: DataclassState) -> dict:\n",
        "    print(\"---Node 2---\")\n",
        "    return {\"mood\": \"happy\"}\n",
        "\n",
        "def node_3(state: DataclassState) -> dict:\n",
        "    print(\"---Node 3---\")\n",
        "    return {\"mood\": \"sad\"}\n",
        "\n",
        "# Build graph\n",
        "builder: StateGraph = StateGraph(DataclassState)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "builder.add_node(\"node_2\", node_2)\n",
        "builder.add_node(\"node_3\", node_3)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_conditional_edges(\"node_1\", decide_mood)\n",
        "builder.add_edge(\"node_2\", END)\n",
        "builder.add_edge(\"node_3\", END)\n",
        "\n",
        "# Add\n",
        "graph: DataclassState = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "06beb50a-4878-4d7e-ac6c-d60a0f417eb3",
      "metadata": {
        "id": "06beb50a-4878-4d7e-ac6c-d60a0f417eb3"
      },
      "source": [
        "We invoke with a `dataclass` to set the initial values of each key / channel in our state!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "8c042325-e93d-43e1-9ac7-a0e20c2fb08d",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "8c042325-e93d-43e1-9ac7-a0e20c2fb08d",
        "outputId": "0c554722-2fc9-4eae-f368-8bb5d18a743a"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n",
            "---Node 2---\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'name': 'Lance is ... ', 'mood': 'happy'}"
            ]
          },
          "metadata": {},
          "execution_count": 13
        }
      ],
      "source": [
        "graph.invoke(DataclassState(name=\"Lance\",mood=\"sad\"))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "e0f3e664",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "e0f3e664",
        "outputId": "45e92296-13d4-4b17-d395-ad732ad7c0a6"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n",
            "---Node 3---\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'name': 'Lance is ... ', 'mood': 'sad'}"
            ]
          },
          "metadata": {},
          "execution_count": 14
        }
      ],
      "source": [
        "graph.invoke({\"name\":\"Lance\", \"mood\": \"still not enforced\", \"random_field\": \"ser\"})"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2405e49b-e786-4bf9-ac85-1fb941d01bcd",
      "metadata": {
        "id": "2405e49b-e786-4bf9-ac85-1fb941d01bcd"
      },
      "source": [
        "## Pydantic\n",
        "\n",
        "As mentioned, `TypedDict` and `dataclasses` provide type hints but they don't enforce types at runtime.\n",
        "\n",
        "This means you could potentially assign invalid values without raising an error!\n",
        "\n",
        "For example, we can set `mood` to `mad` even though our type hint specifies `mood: list[Literal[\"happy\",\"sad\"]]`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "522fcc76-abf7-452a-9d7b-000e06942d94",
      "metadata": {
        "id": "522fcc76-abf7-452a-9d7b-000e06942d94"
      },
      "outputs": [],
      "source": [
        "dataclass_instance = DataclassState(name=\"Lance\", mood=\"mad\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4f095c3a-96b5-4318-9303-20424b4455e9",
      "metadata": {
        "id": "4f095c3a-96b5-4318-9303-20424b4455e9"
      },
      "source": [
        "[Pydantic](https://docs.pydantic.dev/latest/api/base_model/) is a data validation and settings management library using Python type annotations.\n",
        "\n",
        "It's particularly well-suited [for defining state schemas in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/state-model/) due to its validation capabilities.\n",
        "\n",
        "Pydantic can perform validation to check whether data conforms to the specified types and constraints at runtime."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "62e8720e-217f-4b98-837a-af45c3fa577f",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "62e8720e-217f-4b98-837a-af45c3fa577f",
        "outputId": "83f9d89a-0c45-405d-c17e-941833c815b3"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Validation Error: 1 validation error for PydanticState\n",
            "mood\n",
            "  Input should be 'happy' or 'sad' [type=literal_error, input_value='mad', input_type=str]\n",
            "    For further information visit https://errors.pydantic.dev/2.9/v/literal_error\n"
          ]
        }
      ],
      "source": [
        "from pydantic import BaseModel, field_validator, ValidationError\n",
        "\n",
        "class PydanticState(BaseModel):\n",
        "    name: str\n",
        "    mood: Literal[\"happy\", \"sad\"]\n",
        "\n",
        "    @field_validator('mood')\n",
        "    @classmethod\n",
        "    def validate_mood(cls, value):\n",
        "        # Ensure the mood is either \"happy\" or \"sad\"\n",
        "        if value not in [\"happy\", \"sad\"]:\n",
        "            raise ValueError(\"Each mood must be either 'happy' or 'sad'\")\n",
        "        return value\n",
        "\n",
        "try:\n",
        "    state = PydanticState(name=\"John Doe\", mood=\"mad\")\n",
        "except ValidationError as e:\n",
        "    print(\"Validation Error:\", e)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f29913ca-0295-48eb-af4e-cae515dd9a9c",
      "metadata": {
        "id": "f29913ca-0295-48eb-af4e-cae515dd9a9c"
      },
      "source": [
        "We can use `PydanticState` in our graph seamlessly."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "91db3393-b7f8-46e5-8129-0e7539b2804c",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 350
        },
        "id": "91db3393-b7f8-46e5-8129-0e7539b2804c",
        "outputId": "90935407-7cd6-4b97-8353-7c1659422faa"
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ],
      "source": [
        "\n",
        "def node_1(state: PydanticState):\n",
        "    print(\"---Node 1---\")\n",
        "    return {\"name\": state.name + \" is ... \"}\n",
        "\n",
        "def node_2(state: PydanticState):\n",
        "    print(\"---Node 2---\")\n",
        "    return {\"mood\": \"happy\"}\n",
        "\n",
        "def node_3(state: PydanticState):\n",
        "    print(\"---Node 3---\")\n",
        "    return {\"mood\": \"sad\"}\n",
        "\n",
        "def decide_mood(state: PydanticState) -> Literal[\"node_2\", \"node_3\"]:\n",
        "\n",
        "    # Here, let's just do a 50 / 50 split between nodes 2, 3\n",
        "    if random.random() < 0.5:\n",
        "\n",
        "        # 50% of the time, we return Node 2\n",
        "        return \"node_2\"\n",
        "\n",
        "    # 50% of the time, we return Node 3\n",
        "    return \"node_3\"\n",
        "\n",
        "# Build graph\n",
        "builder: StateGraph = StateGraph(PydanticState)\n",
        "builder.add_node(\"node_1\", node_1)\n",
        "builder.add_node(\"node_2\", node_2)\n",
        "builder.add_node(\"node_3\", node_3)\n",
        "\n",
        "# Logic\n",
        "builder.add_edge(START, \"node_1\")\n",
        "builder.add_conditional_edges(\"node_1\", decide_mood)\n",
        "builder.add_edge(\"node_2\", END)\n",
        "builder.add_edge(\"node_3\", END)\n",
        "\n",
        "# Add\n",
        "graph: CompiledStateGraph = builder.compile()\n",
        "\n",
        "# View\n",
        "display(Image(graph.get_graph().draw_mermaid_png()))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "e96c78be-b483-4fa4-949b-62d4274e97ac",
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "e96c78be-b483-4fa4-949b-62d4274e97ac",
        "outputId": "2f8f3a55-516a-4fcb-bdae-1556eaeb35ea"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "---Node 1---\n",
            "---Node 2---\n"
          ]
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "{'name': 'Lance is ... ', 'mood': 'happy'}"
            ]
          },
          "metadata": {},
          "execution_count": 18
        }
      ],
      "source": [
        "graph.invoke(PydanticState(name=\"Lance\",mood=\"sad\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "### A Pydantic React Agent"
      ],
      "metadata": {
        "id": "HGkgC9QTbpvm"
      },
      "id": "HGkgC9QTbpvm"
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "8119232a-7d56-4abc-b0ef-18bf5f0cc9fd",
      "metadata": {
        "id": "8119232a-7d56-4abc-b0ef-18bf5f0cc9fd"
      },
      "outputs": [],
      "source": [
        "from langchain_core.tools import tool\n",
        "\n",
        "\n",
        "@tool\n",
        "def search(query: str) -> str:\n",
        "    \"\"\"Call to surf the web.\"\"\"\n",
        "    # This is a placeholder for the actual implementation\n",
        "    # Don't let the LLM know this though 😊\n",
        "    return \"The answer to your question lies within.\"\n",
        "\n",
        "\n",
        "tools = [search]"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langgraph.prebuilt import ToolNode\n",
        "\n",
        "tool_node: ToolNode = ToolNode(tools)"
      ],
      "metadata": {
        "id": "x8xBWydGb3MD"
      },
      "id": "x8xBWydGb3MD",
      "execution_count": 31,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain_google_genai import ChatGoogleGenerativeAI\n",
        "llm: ChatGoogleGenerativeAI = ChatGoogleGenerativeAI(model = \"gemini-1.5-flash\")\n",
        "\n",
        "model: ChatGoogleGenerativeAI = llm.bind_tools(tools)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "X_U60T22cAtV",
        "outputId": "c4047628-1f9c-47d4-999b-bd13165bba73"
      },
      "id": "X_U60T22cAtV",
      "execution_count": 32,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stderr",
          "text": [
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n",
            "WARNING:langchain_google_genai._function_utils:Key 'title' is not supported in schema, ignoring\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "import operator\n",
        "from typing import Annotated, Sequence\n",
        "\n",
        "from langchain_core.messages import BaseMessage\n",
        "\n",
        "from pydantic import BaseModel\n",
        "\n",
        "class AgentState(BaseModel):\n",
        "    messages: Annotated[Sequence[BaseMessage], operator.add]"
      ],
      "metadata": {
        "id": "iPv2YpGsb298"
      },
      "id": "iPv2YpGsb298",
      "execution_count": 33,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "# Define the function that determines whether to continue or not\n",
        "def should_continue(state: AgentState) -> Literal[\"end\", \"continue\"]:\n",
        "    messages = state.messages\n",
        "    last_message = messages[-1]\n",
        "    # If there is no function call, then we finish\n",
        "    if not last_message.tool_calls:\n",
        "        return \"end\"\n",
        "    # Otherwise if there is, we continue\n",
        "    else:\n",
        "        return \"continue\"\n",
        "\n",
        "\n",
        "# Define the function that calls the model\n",
        "def call_model(state: AgentState):\n",
        "    messages = state.messages\n",
        "    response = model.invoke(messages)\n",
        "    # We return a list, because this will get added to the existing list\n",
        "    return {\"messages\": [response]}"
      ],
      "metadata": {
        "id": "e-DENbkqccnc"
      },
      "id": "e-DENbkqccnc",
      "execution_count": 35,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "from langgraph.graph import END, StateGraph, START\n",
        "\n",
        "# Define a new graph\n",
        "workflow: StateGraph = StateGraph(AgentState)\n",
        "\n",
        "# Define the two nodes we will cycle between\n",
        "workflow.add_node(\"agent\", call_model)\n",
        "workflow.add_node(\"action\", tool_node)\n",
        "\n",
        "# Set the entrypoint as `agent`\n",
        "# This means that this node is the first one called\n",
        "workflow.add_edge(START, \"agent\")\n",
        "\n",
        "# We now add a conditional edge\n",
        "workflow.add_conditional_edges(\n",
        "    # First, we define the start node. We use `agent`.\n",
        "    # This means these are the edges taken after the `agent` node is called.\n",
        "    \"agent\",\n",
        "    # Next, we pass in the function that will determine which node is called next.\n",
        "    should_continue,\n",
        "    # Finally we pass in a mapping.\n",
        "    # The keys are strings, and the values are other nodes.\n",
        "    # END is a special node marking that the graph should finish.\n",
        "    # What will happen is we will call `should_continue`, and then the output of that\n",
        "    # will be matched against the keys in this mapping.\n",
        "    # Based on which one it matches, that node will then be called.\n",
        "    {\n",
        "        # If `tools`, then we call the tool node.\n",
        "        \"continue\": \"action\",\n",
        "        # Otherwise we finish.\n",
        "        \"end\": END,\n",
        "    },\n",
        ")\n",
        "\n",
        "# We now add a normal edge from `tools` to `agent`.\n",
        "# This means that after `tools` is called, `agent` node is called next.\n",
        "workflow.add_edge(\"action\", \"agent\")\n",
        "\n",
        "# Finally, we compile it!\n",
        "# This compiles it into a LangChain Runnable,\n",
        "# meaning you can use it as you would any other runnable\n",
        "app: CompiledStateGraph = workflow.compile()"
      ],
      "metadata": {
        "id": "kRn2yuO7ckZt"
      },
      "id": "kRn2yuO7ckZt",
      "execution_count": 36,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "app.get_graph()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "CGt2_9I8c0CX",
        "outputId": "7a9faaf6-069c-46b2-dbd5-5f2aef112198"
      },
      "id": "CGt2_9I8c0CX",
      "execution_count": 37,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "Graph(nodes={'__start__': Node(id='__start__', name='__start__', data=<class '__main__.AgentState'>, metadata=None), 'agent': Node(id='agent', name='agent', data=agent(tags=None, recurse=True, func_accepts_config=False, func_accepts={'writer': False}), metadata=None), 'action': Node(id='action', name='action', data=tools(tags=None, recurse=True, func_accepts_config=True, func_accepts={'writer': False}, tools_by_name={'search': StructuredTool(name='search', description='Call to surf the web.', args_schema=<class 'langchain_core.utils.pydantic.search'>, func=<function search at 0x786cf81212d0>)}, handle_tool_errors=True), metadata=None), '__end__': Node(id='__end__', name='__end__', data=<class '__main__.AgentState'>, metadata=None)}, edges=[Edge(source='__start__', target='agent', data=None, conditional=False), Edge(source='action', target='agent', data=None, conditional=False), Edge(source='agent', target='action', data='continue', conditional=True), Edge(source='agent', target='__end__', data='end', conditional=True)])"
            ]
          },
          "metadata": {},
          "execution_count": 37
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from IPython.display import Image, display\n",
        "\n",
        "display(Image(app.get_graph().draw_mermaid_png()))"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 290
        },
        "id": "Ip5FiNyrcxqy",
        "outputId": "79615c2b-f49b-477f-ee7d-830fdfd371de"
      },
      "id": "Ip5FiNyrcxqy",
      "execution_count": 38,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/jpeg": "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\n",
            "text/plain": [
              "<IPython.core.display.Image object>"
            ]
          },
          "metadata": {}
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "from langchain_core.messages import HumanMessage\n",
        "\n",
        "inputs = {\"messages\": [HumanMessage(content=\"what is the weather in sf\")]}\n",
        "for chunk in app.stream(inputs, stream_mode=\"values\"):\n",
        "    chunk[\"messages\"][-1].pretty_print()"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "VM2P8rBEc4hi",
        "outputId": "f2a47087-d79e-486e-de40-3f6697deb461"
      },
      "id": "VM2P8rBEc4hi",
      "execution_count": 39,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "================================\u001b[1m Human Message \u001b[0m=================================\n",
            "\n",
            "what is the weather in sf\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "Tool Calls:\n",
            "  search (3fbc3d07-444e-4eea-af73-88fad300d771)\n",
            " Call ID: 3fbc3d07-444e-4eea-af73-88fad300d771\n",
            "  Args:\n",
            "    query: weather in sf\n",
            "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
            "Name: search\n",
            "\n",
            "The answer to your question lies within.\n",
            "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
            "\n",
            "I am sorry, I cannot provide you with the weather information. I do not have access to real-time data, like weather.\n"
          ]
        }
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.2"
    },
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}